/*
 * =====================================================================================
 *
 *       Filename:  tds.cpp
 *
 *    Description:  freetds 封装
 *
 *        Version:  1.0
 *        Created:  09/18/2017 11:03:31 AM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  YOUR NAME (), 
 *   Organization:  
 *
 * =====================================================================================
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//#include <iostream>

#include "tds.h"

using namespace std;

struct TdsField
{
    string           m_colname;
    unsigned int     m_coltype;
    unsigned int     m_collen;
    char             *m_colbuf;
    int              m_colstatus;
};

static void ReleaseFields(TdsField* pFields,int index);

CTds::CTds():m_proc(NULL),m_mh(NULL),m_eh(NULL)
{}

CTds::~CTds()
{
    if (m_proc != NULL)
        dbclose(m_proc);
    dbexit();
}

bool CTds::DbConnect()
{
    if (m_proc != NULL)
    {
        dbclose(m_proc);
        m_proc = NULL;
    }
    
    bool ret = false;
    dbinit();
    // handlers puts here

    if (m_eh != NULL)
        dberrhandle(m_eh);

    if (m_mh != NULL)
        dbmsghandle(m_mh);

    LOGINREC *loginrec = dblogin();
    DBSETLUSER(loginrec, m_dbCfg.user.c_str());
    DBSETLPWD(loginrec, m_dbCfg.passwd.c_str());
    dbsetlogintime(m_dbCfg.logintimeout);
    dbsettime(m_dbCfg.rwtimeout);

    m_proc = dbopen(loginrec, m_dbCfg.server.c_str());
    if (m_proc == FAIL)
    {
        dbloginfree(loginrec);
        m_proc = NULL;
        return ret;
    }
    if(dbuse(m_proc, m_dbCfg.dbname.c_str()) == FAIL)
    {
        dbloginfree(loginrec);
        return ret;
    }

    dbloginfree(loginrec);
    return true;
}

bool CTds::IsConnected()
{
    // timeout:0 return immediately, -1 wait util a response occurs
    // int reason = 0;
    // reason:
    // DBRESULT server responded.
    // DBNOTIFICATION registered procedure notification has arrived.
    // dbpoll() the registered handler, if any, before it returns.
    // DBTIMEOUT milliseconds elapsed before the server responded.
    // DBINTERRUPT operating-system interrupt occurred before the server responded
    // dbpoll not implemented 
    // if ( dbpoll(m_proc,0,NULL,&reason) == FAIL)
    if ( dbdead(m_proc) == TRUE)
    {
        return false;
    }

    return true;
}

bool CTds::Init(const string& server,const string& dbname,const string& user,const string& passwd,
            unsigned int loginTimeout,unsigned int rwTimeout,
            tds_msg_handler mh,tds_err_handler eh)
{
    m_dbCfg.server = server;
    m_dbCfg.dbname = dbname;
    m_dbCfg.user   = user;
    m_dbCfg.passwd = passwd;
    m_dbCfg.logintimeout = loginTimeout;
    m_dbCfg.rwtimeout = rwTimeout;
    
    m_mh = mh;
    m_eh = eh;

    return DbConnect();
}

static void ReleaseFields(TdsField* pFields,int index)
{
    for (int i = 0; i <= index; ++i)
    {
        delete [] pFields[i].m_colbuf;
    }
    delete [] pFields;
}

void CTds::Execute(const string& cmd,map<int,vector< map<string,string> > >& results)
{
    if (!IsConnected() && !DbConnect())
        return;
    
    if ( dbcmd(m_proc, cmd.c_str()) == FAIL )
        return;

    if (dbsqlexec(m_proc) == FAIL)
        return;

    DBINT retCode = 0;
    // 可能多个结果集
    int numSet = 0;
    while( (retCode = dbresults(m_proc)) != NO_MORE_RESULTS)
    {
        int ncols = dbnumcols(m_proc);    //返回查询的结果集数量
        TdsField *pFields = new TdsField[ncols];
        for (int colindex = 0; colindex < ncols; ++colindex ) {
            pFields[colindex].m_colname = string(dbcolname(m_proc, colindex+1));   //返回结果集列名
            pFields[colindex].m_coltype = dbcoltype(m_proc, colindex+1);           //返回结果集列数据类型
            pFields[colindex].m_collen  = dbcollen(m_proc, colindex+1);            //返回列最大长度
            if (pFields[colindex].m_coltype != SYBCHAR)
            {
                pFields[colindex].m_collen  = dbprcollen(m_proc, colindex+1);      //返回列最大长度
            }
            pFields[colindex].m_collen += 1;
            pFields[colindex].m_colbuf = new char[pFields[colindex].m_collen];
            memset(pFields[colindex].m_colbuf,0,pFields[colindex].m_collen);

            //将sql查询到的列数据绑定到一个变量
            retCode  = dbbind(m_proc, colindex+1, NTBSTRINGBIND,
                    pFields[colindex].m_collen, (BYTE*)pFields[colindex].m_colbuf);
            if ( retCode != SUCCEED)
            {
                ReleaseFields(pFields,colindex);
                return;
            }
 
            retCode = dbnullbind(m_proc, colindex+1, &pFields[colindex].m_colstatus);
            if ( retCode != SUCCEED)
            {
                ReleaseFields(pFields,colindex);
                return;
            }
        }
        vector<map<string,string> > vRecords;
        results[numSet]= vRecords;
        while ((retCode = dbnextrow(m_proc)) != NO_MORE_ROWS)
        {
            switch (retCode)
            {
                case REG_ROW:
                {
                    map<string,string> vOneRec;
                    for (int i = 0; i < ncols; ++i)
                    {
                        const char *buffer = pFields[i].m_colstatus == -1?"NULL" : pFields[i].m_colbuf;
                        vOneRec[pFields[i].m_colname] = string(buffer);
                    }
                    results[numSet].push_back(vOneRec);
                    break;
                }
                case BUF_FULL:
                {
                    ;
                }
                case FAIL:
                {
                    ReleaseFields(pFields,ncols-1);
                    return;
                }
                // 其他值忽略
                default:
                    break;
            }
        }
        ++numSet;
        ReleaseFields(pFields,ncols-1);
    }
}

void CTds::Execute(const string& cmd,map<int,vector< vector<string> > >& results)
{
    if (!IsConnected() && !DbConnect())
        return;
    
    if ( dbcmd(m_proc, cmd.c_str()) == FAIL )
        return;

    if (dbsqlexec(m_proc) == FAIL)
        return;
    
    DBINT retCode = 0;
    // 可能多个结果集
    int numSet = 0;
    while( (retCode = dbresults(m_proc)) != NO_MORE_RESULTS)
    {
        int ncols = dbnumcols(m_proc);    //返回查询的结果集数量
        TdsField *pFields = new TdsField[ncols];
        for (int colindex = 0; colindex < ncols; ++colindex ) {
            pFields[colindex].m_colname = string(dbcolname(m_proc, colindex+1));   //返回结果集列名
            pFields[colindex].m_coltype = dbcoltype(m_proc, colindex+1);           //返回结果集列数据类型
            pFields[colindex].m_collen  = dbcollen(m_proc, colindex+1);            //返回列最大长度
            if (pFields[colindex].m_coltype != SYBCHAR)
            {
                pFields[colindex].m_collen  = dbprcollen(m_proc, colindex+1);      //返回列最大长度
            }
            pFields[colindex].m_collen += 1;

            pFields[colindex].m_colbuf = new char[pFields[colindex].m_collen];
            memset(pFields[colindex].m_colbuf,0,pFields[colindex].m_collen);

            //将sql查询到的列数据绑定到一个变量
            retCode  = dbbind(m_proc, colindex+1, NTBSTRINGBIND,
                    pFields[colindex].m_collen, (BYTE*)pFields[colindex].m_colbuf);
            if ( retCode != SUCCEED)
            {
                ReleaseFields(pFields,colindex);
                return;
            }
 
            retCode = dbnullbind(m_proc, colindex+1, &pFields[colindex].m_colstatus);
            if ( retCode != SUCCEED)
            {
                ReleaseFields(pFields,colindex);
                return;
            }
        }
        vector<vector<string> >  vRecords;
        results[numSet]= vRecords;
        while ((retCode = dbnextrow(m_proc)) != NO_MORE_ROWS)
        {
            switch (retCode)
            {
                case REG_ROW:
                {
                    vector<string> vOneRec;
                    for (int i = 0; i < ncols; ++i)
                    {
                        const char *buffer = pFields[i].m_colstatus == -1?"NULL" : pFields[i].m_colbuf;
                        vOneRec.push_back(string(buffer));
                    }
                    results[numSet].push_back(vOneRec);
                    break;
                }
                case BUF_FULL:
                {
                    ;
                }
                case FAIL:
                {
                    ReleaseFields(pFields,ncols-1);
                    return;
                }
                // 其他值忽略
                default:
                    break;
            }
        }
        ++numSet;
        ReleaseFields(pFields,ncols-1);
    }
}






